/**
 * Copyright (C) 2014
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *         http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */
package psn.shan.demo.xml.enhancedvalidator.data.accessors;

import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.List;
import java.util.Map;

import lombok.Synchronized;
import lombok.extern.slf4j.Slf4j;

import org.jdom2.Element;
import org.jdom2.JDOMException;

import psn.shan.xml.enhancedvalidator.data.AbstractBaseRelation;
import psn.shan.xml.enhancedvalidator.data.Attribute;
import psn.shan.xml.enhancedvalidator.data.CardinalityMismatchException;
import psn.shan.xml.enhancedvalidator.data.GenericRelationAccessor;
import psn.shan.xml.enhancedvalidator.data.Tuple;
import psn.shan.xml.enhancedvalidator.util.XmlUtils;

/**
 * GenericRelationAccessorXMLImpl <br />
 * 
 * Implementation using XML data source
 *
 * @author Shan
 *
 */
@Slf4j
public class GenericRelationAccessorXMLImpl<K, T extends Tuple<?>, R extends AbstractBaseRelation<K, T, Map<K, T>>> extends GenericRelationAccessor<K, T, R> {

	private final Object loadLock = new Object();

	// String Constants
	private static final String DATASOURCE_XML = "DataSource.xml";

	private static final String ELEMENT_RELATION = "relation";
	private static final String ELEMENT_NAME = "name";
	private static final String ELEMENT_TUPLE = "tuple";
	private static final String ELEMENT_ATTRIBUTE = "attribute";

	public GenericRelationAccessorXMLImpl(Class<R> aRelationType) {
		super(aRelationType);
	}

	/*
	 * TODO: Cache the loaded XML file to ensure it is not loaded for each relation
	 */
	@Override
	@Synchronized("loadLock")
	protected void loadRelation() throws JDOMException, IOException, InstantiationException, IllegalAccessException,
			IllegalArgumentException, InvocationTargetException, SecurityException, CardinalityMismatchException {
		log.debug("Attempting ot load relation " + mRelation + " ...");

		Element lSchema = XmlUtils.parseXML(DATASOURCE_XML).getRootElement();

		List<Element> lRelations = lSchema.getChildren(ELEMENT_RELATION, lSchema.getNamespace());

		for (Element lRelation : lRelations) {
			if (lRelation.getChild(ELEMENT_NAME, lRelation.getNamespace()).getValue()
					.equalsIgnoreCase(mRelation.getRelationName())) {
				log.debug("Found relation.");
				processRelation(lRelation);
			}
		}
	}

	private void processRelation(Element aRelation) throws InstantiationException, IllegalAccessException,
			IllegalArgumentException, InvocationTargetException, SecurityException {
		List<Element> lTuples = aRelation.getChildren(ELEMENT_TUPLE, aRelation.getNamespace());

		for (Element lTupleElement : lTuples) {
			T lTuple = mTupleType.newInstance();
			List<Element> lAttributeElements = lTupleElement.getChildren(ELEMENT_ATTRIBUTE, lTupleElement.getNamespace());
			Attribute<?>[] lAttributes = new Attribute<?>[lTuple.getCardinality()];
			int lAttributeIndex = 0;
			for (Element lAttributeElement : lAttributeElements) {
				Constructor<?> lAttributeConstructor = ((Class<?>) mRelation.getAttributeTypes().get(lAttributeIndex).getRawType()).getConstructors()[1];
				String lAttributeName = mRelation.getAttributeNames().get(lAttributeIndex);
				Class<?> lAttributeType = (Class<?>) mRelation.getAttributeTypes().get(lAttributeIndex).getActualTypeArguments()[0];
				log.debug("Attribute " + lAttributeName + " is of type " + lAttributeType);

				lAttributes[lAttributeIndex] = (Attribute<?>) (lAttributeConstructor.newInstance(lAttributeName, lAttributeType.cast(lAttributeElement.getValue())));
				++lAttributeIndex;
			}
			lTuple.addAttributes(lAttributes);
			mRelation.addTuple(lTuple);
			log.debug("Tuple is " + lTuple);
		}
	}
}
